前言

当所有的路由都加载完毕后,就会根据请求的 url 来将请求分发到对应的路由上去。然而,在分发到路由之前还要经过各种中间件的计算。laravel 利用装饰者模式来实现中间件的功能。

从原始装饰者模式到闭包装饰者

装饰者模式是设计模式的一种,主要进行对象的多次处理与过滤,是在开放-关闭原则下实现动态添加或减少功能的一种方式。下面先看一个装饰者模式的例子:

总共有两种咖啡:Decaf、Espresso,另有两种调味品:Mocha、Whip(3种设计的主要差别在于抽象方式不同)

装饰模式分为3个部分:

1,抽象组件 — 对应Coffee类

2,具体组件 — 对应具体的咖啡,如:Decaf,Espresso

3,装饰者 — 对应调味品,如:Mocha,Whip

原始装饰者模式

  1. public interface Coffee
  2. {
  3. public double cost();
  4. }
  5. public class Espresso implements Coffee
  6. {
  7. public double cost()
  8. {
  9. return 2.5;
  10. }
  11. }
  12. public class Dressing implements Coffee
  13. {
  14. private Coffee coffee;
  15. public Dressing(Coffee coffee)
  16. {
  17. this.coffee = coffee;
  18. }
  19. public double cost()
  20. {
  21. return coffee.cost();
  22. }
  23. }
  24. public class Whip extends Dressing {
  25. public Whip(Coffee coffee)
  26. {
  27. super(coffee);
  28. }
  29. public double cost()
  30. {
  31. return super.cost() + 0.1;
  32. }
  33. }
  34. public class Mocha extends Dressing
  35. {
  36. public Mocha(Coffee coffee)
  37. {
  38. super(coffee);
  39. }
  40. public double cost()
  41. {
  42. return super.cost() + 0.5;
  43. }
  44. }

当我们使用装饰者模式的时候:

  1. public class Test {
  2. public static void main(String[] args) {
  3. Coffee coffee = new Espresso();
  4. coffee = new Mocha(coffee);
  5. coffee = new Mocha(coffee);
  6. coffee = new Whip(coffee);
  7. //3.6(2.5 + 0.5 + 0.5 + 0.1)
  8. System.out.println(coffee.cost());
  9. }
  10. }

我们可以看出来,装饰者模式就是利用装饰者类来对具体类不断的进行多层次的处理,首先我们创建了 Espresso 类,然后第一次利用 Mocha 装饰者对 Espresso 咖啡加了摩卡,第二次重复加了摩卡,第三次利用装饰者 WhipEspresso 咖啡加了奶油。每次加入新的调料,装饰者都会对价格 cost 做一些处理(+0.1、+0.5)。

无构造函数的装饰者

我们对这个装饰者进行一些改造:

  1. public class Espresso
  2. {
  3. double cost;
  4. public double cost()
  5. {
  6. $this-> cost = 2.5;
  7. }
  8. }
  9. public class Dressing
  10. {
  11. public double cost(Espresso $espresso)
  12. {
  13. return ($espresso);
  14. }
  15. }
  16. public class Whip extends Dressing
  17. {
  18. public double cost(Espresso $espresso)
  19. {
  20. $espresso->cost = espresso->cost() + 0.1;
  21. return ($espresso);
  22. }
  23. }
  24. public class Mocha extends Dressing
  25. {
  26. public double cost(Espresso $espresso)
  27. {
  28. $espresso->cost = espresso->cost() + 0.5;
  29. return ($espresso);
  30. }
  31. }
  1. public class Test {
  2. public static void main(String[] args) {
  3. Coffee $coffee = new Espresso();
  4. $coffee = (new Mocha())->cost($coffee);
  5. $coffee = (new Mocha())->cost($coffee);
  6. $coffee = (new Whip())->cost($coffee);
  7. //3.6(2.5 + 0.5 + 0.5 + 0.1)
  8. System.out.println(coffee.cost());
  9. }
  10. }

改造后,装饰者类通过函数 cost 来注入具体类 caffee,而不是通过构造函数,这样做有助于自动化进行装饰处理。我们改造后发现,想要对具体类通过装饰类进行处理,需要不断的调用 cost 函数,如果有10个装饰操作,就要手动写10个语句,因此我们继续进行改造:

闭包装饰者模式

  1. public class Espresso
  2. {
  3. double cost;
  4. public double cost()
  5. {
  6. $this-> cost = 2.5;
  7. }
  8. }
  9. public class Dressing
  10. {
  11. public double cost(Espresso $espresso, Closure $closure)
  12. {
  13. return ($espresso);
  14. }
  15. }
  16. public class Whip extends Dressing
  17. {
  18. public double cost(Espresso $espresso, Closure $closure)
  19. {
  20. $espresso->cost = espresso->cost() + 0.1;
  21. return $closure($espresso);
  22. }
  23. }
  24. public class Mocha extends Dressing
  25. {
  26. public double cost(Espresso $espresso, Closure $closure)
  27. {
  28. $espresso->cost = espresso->cost() + 0.5;
  29. return $closure($espresso);
  30. }
  31. }
  1. public class Test {
  2. public static void main(String[] args) {
  3. Coffee $coffee = new Espresso();
  4. $fun = function($coffee$fuc$dressing) {
  5. $dressing->cost($coffee, $fuc);
  6. }
  7. $fuc0 = function($coffee) {
  8. return $coffee;
  9. };
  10. $fuc1 = function($coffee) use ($fuc0, $dressing = (new Mocha(),$fun)) {
  11. return $fun($coffee, $fuc0, $dressing);
  12. }
  13. $fuc2 = function($coffee) use ($fuc1, $dressing = (new Mocha(),$fun)) {
  14. return $fuc($coffee, $fun1, $dressing);
  15. }
  16. $fuc3 = function($coffee) use ($fuc2, $dressing = (new Whip(),$fun)) {
  17. return $fuc($coffee, $fun2, $dressing);
  18. }
  19. $coffee = $fun3($coffee);
  20. //3.6(2.5 + 0.5 + 0.5 + 0.1)
  21. System.out.println(coffee.cost());
  22. }
  23. }

在这次改造中,我们使用了闭包函数,这样做的目的在于,我们只需要最后一句 $fun3($coffee),就可以启动整个装饰链条。

闭包装饰者的抽象化

然而这种改造还不够深入,因为我们还可以把 $fuc1$fuc2$fuc3 继续抽象化为一个闭包函数,这个闭包函数仅仅是参数 $fuc$dressing 每次不同,$coffee 相同,因此改造如下:

  1. public class Test {
  2. public static void main(String[] args) {
  3. Coffee $coffee = new Espresso();
  4. $fun = function($coffee) use ($fuc$dressing) {
  5. $dressing->cost($coffee, $fuc);
  6. }
  7. $fuc = function($fuc$dressing) use ($fun) {
  8. return $fun;
  9. };
  10. $fuc0 = function($coffee) {
  11. return $coffee;
  12. };
  13. $fuc1 = $fuc($fuc0, (new Mocha());
  14. $fuc2 = $fuc($fuc1, (new Mocha());
  15. $fuc3 = $fuc($fuc2, (new Whip());
  16. $coffee = $fun3($coffee);
  17. //3.6(2.5 + 0.5 + 0.5 + 0.1)
  18. System.out.println(coffee.cost());
  19. }
  20. }

这次,我们把之前的闭包分为两个部分,$fun 负责具体类的参数传递,$fuc负责装饰者和闭包函数的参数传递。在最后一句 $fun3,只需要传递一个具体类,就可以启动整个装饰链条。

闭包装饰者的自动化

到这里,我们还有一件事没有完成,那就是 $fuc1$fuc2$fuc3 这些闭包的构建还是手动的,我们需要将这个过程改为自动的:

  1. public class Test {
  2. public static void main(String[] args) {
  3. Coffee $coffee = new Espresso();
  4. $fun = function($coffee) use ($fuc$dressing) {
  5. $dressing->cost($coffee, $fuc);
  6. }
  7. $fuc = function($fuc$dressing) use ($fun) {
  8. return $fun;
  9. };
  10. $fuc0 = function($coffee) {
  11. return $coffee;
  12. };
  13. $fucn = array_reduce(
  14. [(new Mocha(),(new Mocha(),(new Whip()], $fuc, $fuc0
  15. );
  16. $coffee = $fucn($coffee);
  17. //3.6(2.5 + 0.5 + 0.5 + 0.1)
  18. System.out.println(coffee.cost());
  19. }
  20. }

laravel的闭包装饰者——Pipeline

上一章我们说到了路由的注册启动与加载过程,这个过程由 bootstrap() 完成。当所有的路由加载完毕后,就要进行各种中间件的处理了:

  1. protected function sendRequestThroughRouter($request)
  2. {
  3. $this->app->instance('request', $request);
  4. Facade::clearResolvedInstance('request');
  5. $this->bootstrap();
  6. return (new Pipeline($this->app))
  7. ->send($request)
  8. ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware)
  9. ->then($this->dispatchToRouter());
  10. }
  11. public function shouldSkipMiddleware()
  12. {
  13. return $this->bound('middleware.disable') &&
  14. $this->make('middleware.disable') === true;
  15. }

laravel 的中间件处理由 Pipeline 来完成,它是一个闭包装饰者模式,其中

  • request 是具体类,相当于我们上面的 caffee 类;
  • middleware 中间件是装饰者类,相当于上面的 dressing 类;

我们先看看这个类内部的代码:

  1. class Pipeline implements PipelineContract
  2. {
  3. public function __construct(Container $container = null)
  4. {
  5. $this->container = $container;
  6. }
  7. public function send($passable)
  8. {
  9. $this->passable = $passable;
  10. return $this;
  11. }
  12. public function through($pipes)
  13. {
  14. $this->pipes = is_array($pipes) ? $pipes : func_get_args();
  15. return $this;
  16. }
  17. public function then(Closure $destination)
  18. {
  19. $pipeline = array_reduce(
  20. array_reverse($this->pipes), $this->carry(), $this->prepareDestination($destination)
  21. );
  22. return $pipeline($this->passable);
  23. }
  24. protected function prepareDestination(Closure $destination)
  25. {
  26. return function ($passable) use ($destination) {
  27. return $destination($passable);
  28. };
  29. }
  30. protected function carry()
  31. {
  32. return function ($stack, $pipe) {
  33. return function ($passable) use ($stack, $pipe) {
  34. if ($pipe instanceof Closure) {
  35. return $pipe($passable, $stack);
  36. } elseif (! is_object($pipe)) {
  37. list($name, $parameters) = $this->parsePipeString($pipe);
  38. $pipe = $this->getContainer()->make($name);
  39. $parameters = array_merge([$passable, $stack], $parameters);
  40. } else {
  41. $parameters = [$passable, $stack];
  42. }
  43. return $pipe->{$this->method}(...$parameters);
  44. };
  45. };
  46. }
  47. }

pipeline 的构造和我们上面所讲的闭包装饰者相同,我们着重来看 carry() 函数的代码:

  1. function ($stack, $pipe) {
  2. ...
  3. }

最外层的闭包相当于上个章节的 $fuc,

  1. function ($passable) use ($stack, $pipe) {
  2. ...
  3. }

里面的这一层比闭包型党与上个章节的 $fun

prepareDestination 这个函数相当于上面的 $fuc0,

  1. if ($pipe instanceof Closure) {
  2. return $pipe($passable, $stack);
  3. } elseif (! is_object($pipe)) {
  4. list($name, $parameters) = $this->parsePipeString($pipe);
  5. $pipe = $this->getContainer()->make($name);
  6. $parameters = array_merge([$passable, $stack], $parameters);
  7. } else {
  8. $parameters = [$passable, $stack];
  9. }
  10. return $pipe->{$this->method}(...$parameters);

这一部分相当于上个章节的 $dressing->cost($coffee, $fuc);,这部分主要解析中间件 handle() 函数的参数:

  1. public function via($method)
  2. {
  3. $this->method = $method;
  4. return $this;
  5. }
  6. protected function parsePipeString($pipe)
  7. {
  8. list($name, $parameters) = array_pad(explode(':', $pipe, 2), 2, []);
  9. if (is_string($parameters)) {
  10. $parameters = explode(',', $parameters);
  11. }
  12. return [$name, $parameters];
  13. }

这样,laravel 就实现了中间件对 request 的层层处理。